package com.msmall.base.dao.hibernate;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.impl.CriteriaImpl;
import org.hibernate.transform.ResultTransformer;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;

import com.msmall.base.dao.BaseDao;
import com.msmall.base.pager.MatchType;
import com.msmall.base.pager.PaginationSupport;
import com.msmall.base.pager.PropertyFilter;
import com.msmall.base.pager.ReflectionUtil;


@SuppressWarnings("unchecked")
public class BaseDaoHibernate <T, PK extends Serializable> extends HibernateDaoSupport implements BaseDao<T, PK>{
	
	protected final Log log = LogFactory.getLog(getClass());
	
	private Class<T> persistentClass;

	public BaseDaoHibernate() {
    }
    /**
     * Constructor that takes in a class to see which type of entity to persist
     * @param persistentClass the class type you'd like to persist
     */
    public BaseDaoHibernate(final Class<T> persistentClass) {
        this.persistentClass = persistentClass;
    }

	public T get(PK id) {
		T entity = (T) super.getHibernateTemplate().get(this.persistentClass, id);

        if (entity == null) {
            log.warn("Uh oh, '" + this.persistentClass + "' object with id '" + id + "' not found...");
            return null;
        }

        return entity;
	}

	public Object get(Class clazz, Serializable id) {
		   Object o = getHibernateTemplate().get(clazz, id);

	        if (o == null) {
	        	return null;
	        }

	        return o;
	}
	
	public List<T> findByNamedQuery(String queryName, Map<String, Object> queryParams) {
		       String []params = new String[queryParams.size()];
		       Object []values = new Object[queryParams.size()];
		       int index = 0;
		       Iterator<String> i = queryParams.keySet().iterator();
		       while (i.hasNext()) {
		           String key = i.next();
		           params[index] = key;
		           values[index++] = queryParams.get(key);
		       }
		       return getHibernateTemplate().findByNamedQueryAndNamedParam(
		           queryName, 
		           params, 
		           values);
		   }
	public List<T> getAll() {
		 return super.getHibernateTemplate().loadAll(this.persistentClass);
	}

	public List getAll(Class clazz) {
		return getHibernateTemplate().loadAll(clazz);
	}

	public T save(T object) {
		return (T) super.getHibernateTemplate().merge(object);
	}
	
	public Object saveObject(Object obj){
		return super.getHibernateTemplate().merge(obj);
	}
	public void remove(PK id) {
		 super.getHibernateTemplate().delete(this.get(id));
	}

	public void remove(Class clazz, Serializable id) {
		getHibernateTemplate().delete(get(clazz, id));		
	}
	public void remove(Object obj) {
		getHibernateTemplate().delete(obj);
	}
	public void removeAll(Collection entities) {
		getHibernateTemplate().deleteAll(entities);
	}
	public boolean exists(PK id) {
		T entity = (T) super.getHibernateTemplate().get(this.persistentClass, id);
        return entity != null;
	}
	public boolean exists(Class clazz, Serializable id) {
		T entity = (T) super.getHibernateTemplate().get(clazz, id);
        return entity != null;
	}
	
	public boolean contains(Object obj){
		return super.getHibernateTemplate().contains(obj);
	}

	public List getByProperty(Class clazz, String propertyName, MatchType matchType, String propertyValue) {
		Session session = getSession();
		Criteria criteria = session.createCriteria(clazz);
		Criterion c = buildPropertyFilterCriterion(propertyName, propertyValue, matchType, clazz);
		criteria.add(c);
		List list = criteria.list();
		releaseSession(session);
		return list;
	}
	public List getByProperty(Class clazz, String[] propertyNames, MatchType[] matchTypes, String[] propertyValues) {
		Session session = getSession();
		Criteria criteria = session.createCriteria(clazz);
		for(int i = 0; i< propertyNames.length; i ++){
			Criterion c = buildPropertyFilterCriterion(propertyNames[i], propertyValues[i], matchTypes[i], clazz);
			criteria.add(c);
		}
		List list = criteria.list();
		releaseSession(session);
		return list;
	}
	/**
	 * 如果设置了queryString 则order by 需要在 queryString中写好
	 */
	public void getPagination(PaginationSupport page){
		try{
			Assert.notNull(page, "page不能为空");
			 // 如果用户自己写了查询语句，首先执行用户自己的语句
			if(page.getQueryString() != null && !"".equals(page.getQueryString())){
				getManualPagination(page);
				return;
			}
			// 如果条件查询
			if(null != page.getFilters() && page.getFilters().size() > 0){
				getFilterPagination(page);
				return;
			}
			// 仅设置了classname的简单查询
			getEasyPagination(page);
		}catch(Exception e){
			log.error(e);
		}
 		
    }
	public void getPagination(PaginationSupport page, Criterion... criterions){
		Assert.notNull(page, "page不能为空");
	//	Criteria c = createCriteria(page.getClazz(), criterions);
		
		Session session = getSession();
		
		Criteria criteria = session.createCriteria(page.getClazz());
		for (Criterion c : criterions) {
			criteria.add(c);
		}
		
		
		countCriteriaResult(page, criteria);

		setPageParameter(criteria, page);
		List items = criteria.list();
		page.setItems(items);
		releaseSession(session);
	}
	/**
	 * 设置分页参数到Criteria对象,辅助函数.
	 */
	protected Criteria setPageParameter(final Criteria c, PaginationSupport page) {
		//hibernate的firstResult的序号从0开始
		c.setFirstResult(page.getStartIndex());
		c.setMaxResults(page.getPageSize());

		if (page.isOrderBySetted()) {
			String[] orderByArray = StringUtils.split(page.getOrderBy(), ',');
			String[] orderArray = StringUtils.split(page.getOrder(), ',');

			Assert.isTrue(orderByArray.length == orderArray.length, "分页多重排序参数中,排序字段与排序方向的个数不相等");

			for (int i = 0; i < orderByArray.length; i++) {
				if (PaginationSupport.ASC.equals(orderArray[i])) {
					c.addOrder(Order.asc(orderByArray[i]));
				} else {
					c.addOrder(Order.desc(orderByArray[i]));
				}
			}
		}
		return c;
	}
	/**
	 * 执行count查询获得本次Criteria查询所能获得的对象总数.
	 */
	protected void countCriteriaResult(PaginationSupport page, final Criteria c) {
		if (page.isAutoCount()) {
			CriteriaImpl impl = (CriteriaImpl) c;
	
			// 先把Projection、ResultTransformer、OrderBy取出来,清空三者后再执行Count操作
			Projection projection = impl.getProjection();
			ResultTransformer transformer = impl.getResultTransformer();
	
			List<CriteriaImpl.OrderEntry> orderEntries = null;
			try {
				orderEntries = (List) ReflectionUtil.getFieldValue(impl, "orderEntries");
				ReflectionUtil.setFieldValue(impl, "orderEntries", new ArrayList());
			} catch (Exception e) {
				log.error("不可能抛出的异常:{}" + e.getMessage());
			}
	
			// 执行Count查询
			int totalCount = (Integer) c.setProjection(Projections.rowCount()).uniqueResult();
	
			// 将之前的Projection,ResultTransformer和OrderBy条件重新设回去
			c.setProjection(projection);
	
			if (projection == null) {
				c.setResultTransformer(CriteriaSpecification.ROOT_ENTITY);
			}
			if (transformer != null) {
				c.setResultTransformer(transformer);
			}
			try {
				ReflectionUtil.setFieldValue(impl, "orderEntries", orderEntries);
			} catch (Exception e) {
				log.error("不可能抛出的异常:{}" + e.getMessage());
			}
			
			page.setTotalCount(totalCount);
		}
	}
	/**
	 * 根据Criterion条件创建Criteria.
	 * 
	 * 本类封装的find()函数全部默认返回对象类型为T,当不为T时使用本函数.
	 * 
	 * @param criterions 数量可变的Criterion.
	 */
	/**
	protected Criteria createCriteria(Class clazz, final Criterion... criterions) {
		Session session = getSession();
		Criteria criteria = session.createCriteria(clazz);
		for (Criterion c : criterions) {
			criteria.add(c);
		}
	//	releaseSession(session);
		return criteria;
	}
	*/
	
	protected void getManualPagination(PaginationSupport page){
		Assert.notNull(page, "page不能为空");
		String querySentence = page.getQueryString();
		Session session = getSession();
		Query query = session.createQuery(querySentence);
 		if(page.getPageSize() > 0){
 			query.setFirstResult(page.getStartIndex())
 				.setMaxResults(page.getPageSize());
 		}
 		List list = query.list();
 		page.setItems(list);
 		
 		releaseSession(session);
 		getPaginationTotalCount(page, querySentence);
	}
	protected void getFilterPagination(PaginationSupport page){
		Criterion[] criterions = buildPropertyFilterCriterions(page);
		getPagination(page, criterions);
	}
	/**
	 * 按属性条件列表创建Criterion数组,辅助函数.
	 */
	protected Criterion[] buildPropertyFilterCriterions(PaginationSupport page) {
		List<PropertyFilter> filters = page.getFilters();
		List<Criterion> criterionList = new ArrayList<Criterion>();
		for (PropertyFilter filter : filters) {
			Criterion criterion = buildPropertyFilterCriterion(filter.getName(), filter.getValue(), filter.getMatchType(), page.getClazz());
			criterionList.add(criterion);
		}
		return criterionList.toArray(new Criterion[criterionList.size()]);
	}
	/**
	 * 按属性条件参数创建Criterion,辅助函数.
	 */
	protected Criterion buildPropertyFilterCriterion(final String propertyName, final Object value,
			final MatchType matchType, final Class clazz) {
		Assert.hasText(propertyName, "propertyName不能为空");
		
		Criterion criterion = null;
		try {
			//按entity property中的类型将字符串转化为实际类型.
			Object realValue = null;
			if(!MatchType.IN.equals(matchType)){
				 realValue = ReflectionUtil.convertValue(value, clazz, propertyName);
			}
			//根据MatchType构造criterion
			if (MatchType.EQ.equals(matchType)) {
				criterion = Restrictions.eq(propertyName, realValue);
			}
			if (MatchType.LIKE.equals(matchType)) {
				criterion = Restrictions.like(propertyName, (String) realValue, MatchMode.ANYWHERE);
			}
			if (MatchType.LE.equals(matchType)) {
				criterion = Restrictions.le(propertyName, realValue);
			}
			if (MatchType.LT.equals(matchType)) {
				criterion = Restrictions.lt(propertyName, realValue);
			}
			if (MatchType.GE.equals(matchType)) {
				criterion = Restrictions.ge(propertyName, realValue);
			}
			if (MatchType.GT.equals(matchType)) {
				criterion = Restrictions.gt(propertyName, realValue);
			}
			if (MatchType.NE.equals(matchType)) {
				criterion = Restrictions.ne(propertyName, realValue);
			}
			if (MatchType.IN.equals(matchType)) {
				criterion = Restrictions.in(propertyName, (Collection)realValue);
			}
		} catch (Exception e) {
			throw ReflectionUtil.convertToUncheckedException(e);
		}
		return criterion;
	}
	protected void getEasyPagination(PaginationSupport page){
		Assert.notNull(page, "page不能为空");
		StringBuilder buffer = new StringBuilder();
		buffer.append("FROM ").append(page.getClazz().getName());
		if (page.isOrderBySetted()) {
			String[] orderByArray = StringUtils.split(page.getOrderBy(), ',');
			String[] orderArray = StringUtils.split(page.getOrder(), ',');

			Assert.isTrue(orderByArray.length == orderArray.length, "分页多重排序参数中,排序字段与排序方向的个数不相等");
				
			buffer.append(" order by ");
			for (int i = 0; i < orderByArray.length; i++) {
				buffer.append(" ").append(orderByArray[i]).append(" ").append(orderArray[i]).append(",");
			}
		}
		String queryString = buffer.toString();
		if (page.isOrderBySetted()) {
			queryString = queryString.substring(0, queryString.length() - 1);
		}
		Session session = getSession();
		Query query = session.createQuery(queryString);
 		if(page.getPageSize() > 0){
 			query.setFirstResult(page.getStartIndex())
 				.setMaxResults(page.getPageSize());
 		}
 		List list = query.list();
 		page.setItems(list);
 		releaseSession(session);
 		getPaginationTotalCount(page, queryString);
		
	}
	protected void getPaginationTotalCount(PaginationSupport page, String querySentence){
		Assert.notNull(page, "page不能为空");
		
		if(page.isAutoCount()){
			String tempSql = querySentence.toLowerCase();
			int fromIndex = tempSql.indexOf("from ");
			if(fromIndex != -1){
				querySentence = querySentence.substring(fromIndex);
			}
			String queryStr = "select count(*) " + querySentence;
			queryStr = StringUtils.substringBefore(queryStr, "order by"); // order by 影响性能
			Session session = getSession();
			Query query = session.createQuery(queryStr);
 	 		Long records = (Long)query.uniqueResult();
 	 		page.setTotalCount(records.intValue());
 	 		releaseSession(session);
		}
	}
}
